# -*- coding: utf-8 -*- 
import numpy as np
from collections import defaultdict

# 召回率 & 准确率
def recall_precision(train, test, recommend):
    """
    召回率：实际为正例的多少被命中了
    准确率：推荐的有多少被命中了
    """
    hit = R_all = P_all = 0
    N = recommend.N
    for user in test.keys():
        if user not in train:
            continue
        # 实际的物品集
        tu = test[user]
        # 获得推荐结果
        rank = recommend.predict(user)
        items = set(rank.keys())
        hit += len(tu & items)
        R_all += len(tu)
        P_all += N
    return hit / R_all, hit / P_all

# 覆盖率
def coverage(train, test, recommend):
    """
    覆盖率：如果所有的产品都被至少推荐给了一个客户，则覆盖率为100%
    """
    recommend_items = set()
    all_items = set()
    for user in test.keys():
        if user not in train:
            continue
        all_items |= train[user]
        # 获取推荐结果
        rank = recommend.predict(user)
        recommend_items |= set(rank.keys())
        # 防止覆盖率超过100%
        all_items |= set(rank.keys())
    return len(recommend_items) / len(all_items)
    
# 流行度
def popularity(train, test, recommend, item_count):
    """
    流行度：如果推荐的都是热门产品，则流行度很低，相应的新颖度很低
    """
    ret = n = 0
    for user in test.keys():
        if user not in train:
            continue
        # 获取推荐结果
        rank = recommend.predict(user)
        items = set(rank.keys())
        for item in items:
            # 物品流行度计数结果
            ret += np.log(1 + item_count.get(item, 0))
            n += 1
    return ret / n

# 多样性
# 余弦相似度
def CosineSim(item_tags, i, j):
    ni = np.sum(np.square(np.array(list(item_tags[i].values()))))
    nj = np.sum(np.square(np.array(list(item_tags[j].values()))))
    ret = sum(item_tags[i][b] * item_tags[j][b] for b in set(item_tags[i].keys()) & set(item_tags[j].keys()))
    return ret / np.sqrt(ni * nj)

def Diversity(train, test, recommend):
    ret = n = 0
    for user in test.keys():
        if user not in train:
            continue
        rank = recommend.predict(user)
        items = set(rank.keys())
        for i, j in combinations(items, 2):
            ret += CosineSim(recommend.item_tags, i, j)
            n += 1
    return ret / n